const exportObject = {
  /**
   *
   * @returns {string} 获得的uuid
   */
  getUuid: () => `${new Date().getTime()}-${~~(Math.random() * 9999999999)}`,
  /**
   * 获取对象的类型
   *
   * @param {*} obj 要检查的对象
   * @returns {string} 'String', 'Object', 'Array'....
   */
  getInstance: function (obj) {
    const result = Object.prototype.toString.call(obj)
    const {
      groups: { type }
    } = /^\[object\s+(?<type>\w+(\d\w)*)\]$/.exec(result)
    return type
  },
  /**
   * 根据最大行数对Array进行分段
   *
   * @param {*[]}[arr=[]] 传入的Array
   * @param {number}[maxRow=[]] 最大行数
   * @returns {Array[]} 返回分段后的Array
   */
  splitArrByRow: function (arr = [], maxRow = 5) {
    maxRow = ~~(((+maxRow) ** 2) ** 0.5) || 1 // 先转换为正整数
    const devider = Math.ceil(arr.length / maxRow) // 例如总公共有6个，每份5个，分成2份
    if (arr.length > maxRow) {
      const arrOut = []
      for (let i = 0; i < devider; i++) {
        const [from, to] = [i * maxRow, (i + 1) * maxRow]
        if (to < arr.length) {
          // 如果范围没超，从from截取到to但不含to
          arrOut.push(arr.slice(from, to))
        } else {
          // 如果是最后一列，截取到结尾
          arrOut.push(arr.slice(from))
        }
      }
      return arrOut
    } else {
      return [arr]
    }
  },
  /**
   * 根据列数数对Array进行分段
   *
   * @param {*[]}[arr=[]] 传入的Array
   * @param {number}[cols=[]] 列数
   * @returns {Array[]} 返回分段后的Array
   */
  splitArrByCol: function (arr = [], cols = 2) {
    cols = ~~(((+cols) ** 2) ** 0.5) || 1 // 先转换为正整数
    const maxRow = Math.ceil(arr.length / cols) // 例如总公共有5个，每份2列，分成3份
    if (arr.length > maxRow) {
      const arrOut = []
      for (let i = 0; i < cols; i++) {
        const [from, to] = [i * maxRow, (i + 1) * maxRow]
        if (to < arr.length) {
          // 如果范围没超，从from截取到to但不含to
          arrOut.push(arr.slice(from, to))
        } else {
          // 如果是最后一列，截取到结尾
          arrOut.push(arr.slice(from))
        }
      }
      return arrOut
    } else {
      return [arr]
    }
  },
  /**
   * 将img标签转为base64
   *
   * @description 在electron, 有些网站需要设置webPreferences-webSecurity为false
   * @description 如果需要导出图片到文件需要设置 {encoding: "base64"}
   * @see {@link https://www.electronjs.org/zh/docs/latest/api/browser-window#new-browserwindowoptions | BrowserWindow设置}
   * @param {Element}img <img>标签
   * @returns {object} - .ext 格式(jpg等,不含".") - .data Base64内容 .base64 包含data:image/png;base64开头的文件
   */
  getImgBase64: function (img) {
    // 创建一个空的canvas元素
    const canvas = document.createElement('canvas')
    canvas.width = img.width
    canvas.height = img.height
    // Copy the image contents to the canvas
    const ctx = canvas.getContext('2d')
    ctx.drawImage(img, 0, 0)
    // Get the data-URL formatted image
    // Firefox supports PNG and JPEG. You could check img.src to
    // guess the original format, but be aware the using "image/jpg"
    // will re-encode the image.
    const dataURL = canvas.toDataURL('image/png')
    const reg = /^data:image\/(?<ext>\w+);base64,/
    const {
      groups: { ext }
    } = reg.exec(dataURL)
    return { data: dataURL.replace(reg, ''), ext, base64: dataURL }
  },
  /**
   * 在页面中是否存在某个元素
   *
   * @param {string}css css选择器
   * @returns {boolean} 该元素是否存在
   */
  exists: function (css) {
    const $ = require('jquery')
    return $(css).length > 0
  },
  /**
   * 显示Url窗口，如果需要跳转，则输入回车
   */
  showUrl: function () {
    const $ = require('jquery')
    $(document.body).prepend(
      `<div style="position:fixed;width: calc(100vw - 20px);bottom:0px;z-index:9999;background-color:#fff;">
        <div class="example-showcase">
          <div class="el-input el-input--default el-input-group el-input-group--append">
            <input
              id="my-preload-common-href"
              class="el-input__inner"
              type="text"
              autocomplete="off"
              placeholder="Please input"
            />
            <div id="my-preload-common-href-go" class="el-input-group__append" style="cursor:pointer;">转到</div>
          </div>
        </div>
      </div>`
    )
    let history = ''
    setInterval(() => {
      history !== window.location.href &&
        $('#my-preload-common-href').val(window.location.href)
      history = window.location.href
    }, 500)
    const cb = () => {
      let inputVal = $('#my-preload-common-href').val()
      ;/https?:\/{2}/i.test(inputVal) || (inputVal = 'http://' + inputVal)
      window.location.href = inputVal
    }
    $('#my-preload-common-href-go').on('click', cb)
    $('#my-preload-common-href').each((i, n) => {
      n.addEventListener('keydown', ({ key }) => /enter/i.test(key) && cb())
    })
  },

  /**
   * 创建一个倒计时窗
   *
   * @param {object}param0 选项
   * @param {string}param0.spanId 倒计时内的span的id
   * @param {number}param0.duration 倒计时的总毫秒数
   * @param {number}param0.interval 倒计时所使用的减小间隔
   * @param {Function}param0.countDownMessage (timeLeft) => 用于显示timeleft的函数，返回字符串
   * @param {Function}param0.beforeStart 在倒计时开始之前运行的内容
   * @param {Function}param0.fail 倒计时取消时运行的内容
   * @param {Function}param0.success 倒计时结束时运行的内容
   * @param {Function}param0.every 每间隔都运行的内容
   * @param {boolean}param0.showCancel 是否显示"取消"
   * @param {boolean}param0.showStart 是否显示"立即"
   * @param {boolean}param0.showRestart 是否显示"重新"
   * @returns {object} [msg=>ElMessage对象] [promise=Promise对象，将会在倒计时结束时resolve]
   */
  startCountDown: function ({
    spanId = 'my-spider-countdown',
    duration = 10 * 1000,
    interval = 100,
    countDownMessage = (timeLeft) => `剩余${timeLeft}毫秒`,
    showCancel = true,
    showStart = true,
    showRestart = false,
    beforeStart = () => 0,
    fail = () => 0,
    success = () => 0,
    every = () => 0
  }) {
    beforeStart()
    const { ElMessage } = require('element-plus')
    const { h } = require('vue')
    const $ = require('jquery')
    const innerMsg = []
    const styleConfig = {
      class: 'el-button el-button--text',
      style: 'display:inline;'
    }
    showRestart &&
      innerMsg.push(
        h('span', { id: `${spanId}Restart`, ...styleConfig }, '重新')
      )
    showStart &&
      innerMsg.push(h('span', { id: `${spanId}Start`, ...styleConfig }, '立即'))
    showCancel &&
      innerMsg.push(
        h('span', { id: `${spanId}Cancel`, ...styleConfig }, '取消')
      )
    const message = h('div', { style: 'line-height:1.5;' }, [
      h(
        'span',
        { id: `${spanId}`, style: 'margin-right:15px;' },
        countDownMessage(duration)
      ),
      ...innerMsg
    ])
    const msg = ElMessage({ duration: duration + 10, message })
    return {
      msg,
      promise: new Promise((resolve, reject) => {
        let _timeLeft = duration
        let _timer = ''
        /**
         *
         */
        function _successCallback() {
          clearInterval(_timer)
          $(`#${spanId}`).text(countDownMessage(0))
          $(`#${spanId}Cancel`).remove()
          $(`#${spanId}Start`).remove()
          msg.close()
          success()
          resolve(1)
        }
        /**
         *
         */
        function _failCallback() {
          clearInterval(_timer)
          $(`#${spanId}Cancel`).remove()
          $(`#${spanId}Start`).remove()
          msg.close()
          fail() // 如果点击了取消就运行fail这个function
          // eslint-disable-next-line prefer-promise-reject-errors
          reject('Countdown Cancelled') // 并且返回catch
        }
        $(`#${spanId}Restart`).click(() => {
          _timeLeft = duration
          $(`#${spanId}`).text(countDownMessage(_timeLeft))
        })
        $(`#${spanId}Start`).click(_successCallback)
        $(`#${spanId}Cancel`).click(_failCallback)
        _timer = setInterval(() => {
          every(_timeLeft)
          if (_timeLeft < interval) {
            _successCallback()
          } else {
            _timeLeft -= interval
            $(`#${spanId}`).text(countDownMessage(_timeLeft))
          }
        }, interval)
      })
    }
  },

  /**
   * 等待多少ms
   *
   * @param {number} interval 单位毫秒
   * @returns {Promise} undefined
   */
  sleep: function (interval) {
    return new Promise((resolve) => {
      setTimeout(resolve, interval)
    })
  },

  /**
   * 在数字前面补0，如果数字已经够大了，就不补了
   *
   * @param {number|string} num - 要计算的数字
   * @param {number} minLength - 补到多少位数
   * @returns {string} - 补充完0的字符串
   */
  keep0s(num, minLength) {
    const isMinus = +num < 0
    let str = (isMinus ? -num : num) + ''
    const { length: len } = str.split('.')[0]
    if (len < minLength) {
      str =
        Array.from({ length: minLength - len })
          .map(() => '0')
          .join('') + str
    }
    return `${isMinus ? '-' : ''}${str}`
  },

  /**
   * @function 将60进制字符串转化成秒
   * @param {object|string}options 第一个参数可以使选项，也可以是时间字符串
   * @param {number}[options.base=60] - 多少进制
   * @param {string|RegExp}[options.delimeter=/[:：]{1}/g] - 分割符
   * @param {...string} args 60进制时间字符串
   * @returns {number} 总秒数
   */
  secOf60s(options, ...args) {
    const times = [options, ...args] // 按值传递
    // 第一个参数是否为Object也就是选项
    const isFirstArgOptions =
      Object.prototype.toString.call(times[0]) === '[object Object]'
    const { base = 60, delimeter = /[:：]{1}/g } = isFirstArgOptions
      ? times.shift()
      : {}
    const reg = /^\s*-\s*/g
    let secs = 0 // 总秒数
    for (const str of times) {
      const sgn = reg.test(str) ? -1 : 1 // 是否为负
      if (str) {
        // eslint-disable-next-line no-unused-vars
        const arr = [
          ...str.replace(reg, '').replace(/\s+/g, '').split(delimeter)
        ]
          .reverse()
          .forEach((ele, i) => (secs += base ** i * sgn * +ele))
      }
    }
    return secs
  },

  /**
   * @function 将总秒数转化为60进制字符串
   * @param {object|number}options - 第一个参数可以是选项，也可以是秒数
   * @param {number}[options.digits=0] - 保留小数点后几位
   * @param {number}[options.colons=1] - 用几个冒号分割
   * @param {number}[options.base=60] - 多少进制
   * @param {string}[options.delimeter=":"] - 分割符
   * @param {boolean}[options.keep0=true] - 是否补0，直到和base的位数一致
   * @param  {...number} args - 数字
   * @returns {string}返回计算结果
   */
  to60sFromSec(options, ...args) {
    const times = [options, ...args] // 按值传递
    // 第一个参数是否为Object也就是选项
    const isFirstArgOptions =
      Object.prototype.toString.call(times[0]) === '[object Object]'
    const {
      digits = 0, //
      colons = 1,
      base = 60,
      delimeter = ':',
      keep0 = true
    } = isFirstArgOptions ? times.shift() : {}
    let total = 0 // 总共有多少秒
    if (Object.prototype.toString.call(times) === '[object Array]') {
      for (const time of times) {
        total += time
      }
    } else {
      total = +times
    }
    const isMinus = total < 0
    isMinus && (total = -total) // 如果为负数，则变为正数
    let arr = Array.from({ length: colons + 1 })
      .map((_, i) => i)
      .reverse()
      .map((i) => {
        if (i === 0) {
          return `${~~(total * 10 ** digits) / 10 ** digits}`
        } else {
          const floored = Math.floor(total / base ** i)
          total %= base ** i
          return floored + ''
        }
      })
    if (keep0) {
      arr = arr.map((e) => exportObject.keep0s(e, `${~~base}`.length))
    }
    return `${isMinus ? '-' : ''}${arr.join(delimeter)}`
  },

  /**
   * @param {object|string|number}options - 第一个参数可以是选项，也可以是秒数
   * @param {number}[options.digits=0] - 保留小数点后几位
   * @param {number}[options.colons=1] - 用几个冒号分割
   * @param {number}[options.base=60] - 多少进制
   * @param {string}[options.delimeter=":"] - 分割符
   * @param {boolean}[options.keep0=true] - 是否补0，直到和base的位数一致
   * @param {...string|number} args 要加的数字或字符串
   * @returns {string}计算结果
   */
  sumOf60s(options, ...args) {
    let total = 0
    const strs = [...arguments].filter(
      (e) => Object.prototype.toString.call(e) === '[object String]'
    ) // 筛选出字符串部分
    const nums = [...arguments].filter(
      (e) => Object.prototype.toString.call(e) === '[object Number]'
    ) // 筛选出数字部分
    nums.forEach((e) => (total += e)) // 计算数字部分相加结果
    if (Object.prototype.toString.call(options) === '[object Object]') {
      // 如果有options
      total += exportObject.secOf60s(options, ...strs)
      return exportObject.to60sFromSec(options, total)
    } else {
      // 如果不带options
      total += exportObject.secOf60s(...strs)
      return exportObject.to60sFromSec(total)
    }
  },

  /**
   * 判断是否为HTMl元素
   *
   * @param {*}obj 任何变量
   * @returns {boolean} 是否为html元素
   */
  isHTMLElement(obj) {
    try {
      const d = document.createElement('div')
      d.appendChild(obj.cloneNode(true))
      return obj.nodeType === 1
    } catch (e) {
      try {
        return obj === window || obj === document
      } catch (error) {
        return false
      }
    }
  },
  getWsAddress() {
    const { protocol, host } = location
    const wsProtocol = protocol.replace('http', 'ws')
    return `${wsProtocol}//${host}`
  },
  /**
   * 批量处理ws.onmessage
   *
   * @param {Function[]}handlers (msg)=>{ ...todo}其中msg已经parse过了
   * @param {WebSocket}ws 传入一个weWebSocket实例
   */
  wsOn(handlers = [], ws = new WebSocket(exportObject.getWsAddress())) {
    ws.onmessage = ({ data }) => {
      for (const func of handlers) {
        try {
          func(JSON.parse(data))
        } catch (error) {
          func(data)
        }
      }
    }
  },
  /**
   * 封装 ws.send
   *
   * @param {*} msg 向服务器发送的对象，会经过JSON.stringify处理
   * @param {WebSocket}ws 传入一个weWebSocket实例
   */
  wsSend(msg, ws = new WebSocket(exportObject.getWsAddress())) {
    ws.send(JSON.stringify(msg))
  },
  /**
   * 由模拟点击来跳转到某个页面
   *
   * @param {string}[href="/"] 要跳转的link
   * @param {string}[target="_blank"] target
   */
  clickOpenLink(href = '/', target = '_blank') {
    const id = 'a' + exportObject.getUuid()
    const $ = require('jquery')
    $(document.body).append(`<a id ="${id}" href="${href}"></a>`)
    $('#' + id).each((i, n) => n.click())
    $('#' + id).remove()
  },
  /**
   * @param {number}percentage 百分比
   * @returns {string}根据百分比返回的颜色字符串
   */
  getColorByPercentage(percentage) {
    if (percentage > 0) {
      const { max } = Math
      const r = 0 + ((255 - 0) * percentage) / max(percentage, 100)
      const g = 240 + ((150 - 240) * percentage) / max(percentage, 100)
      const b = 180 + ((101 - 180) * percentage) / max(percentage, 100)
      return `rgb(${r},${g},${b})`
    } else {
      return '#8696a7'
    }
  },
  /**
   * 用于element-ui的el-table的filter-method
   *
   * @param {*} value No discription
   * @param {*} row No discription
   * @param {*} column No discription
   * @returns {boolean} No discription
   */
  generalFilterHandle(value, row, column) {
    const property = column.property
    return row[property] === value
  },
  /**
   * 将Date对象转为字符串
   *
   * @param {string} [fmt='YY-mm-dd'] <Y,m,d,H,M,S>组成的字符串
   * @param {Date} [date=new Date()] Date对象
   * @returns {string} 返回日期对应的format之后的字符串
   */
  dateFormat(fmt = 'YY-mm-dd', date = new Date()) {
    let ret
    const opt = {
      'Y+': date.getFullYear().toString(), // 年
      'm+': (date.getMonth() + 1).toString(), // 月
      'd+': date.getDate().toString(), // 日
      'H+': date.getHours().toString(), // 时
      'M+': date.getMinutes().toString(), // 分
      'S+': date.getSeconds().toString() // 秒
      // 有其他格式化字符需求可以继续添加，必须转化成字符串
    }
    for (const k in opt) {
      ret = new RegExp('(' + k + ')').exec(fmt)
      if (ret) {
        fmt = fmt.replace(
          ret[1],
          ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0')
        )
      }
    }
    return fmt
  },
  /**
   *
   * @param {string | number | Date} a 被减数
   * @param {string | number | Date} b 减数
   * @returns {number}计算出两个时间的月份差
   */
  getMonthDifference(a, b) {
    if (/(string|number)/i.test(a)) {
      a = new Date(a)
    }
    if (/(string|number)/i.test(b)) {
      b = new Date(b)
    }
    return (
      a.getFullYear() * 12 + a.getMonth() - b.getFullYear() * 12 - b.getMonth()
    )
  }
}
module.exports = exportObject
